package com.nage.engine;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import org.xmlpull.v1.XmlPullParserException;

import android.content.Context;
import android.content.res.XmlResourceParser;
import android.util.Log;

import com.nage.components.collision.Collidable;
import com.nage.components.collision.CollisionManager;
import com.nage.components.collision.CollisionMask;
import com.nage.components.collision.PongBat;
import com.nage.components.display.Background;
import com.nage.components.display.Shape;
import com.nage.components.display.ShapeBuilder;
import com.nage.components.display.Sprite;
import com.nage.components.movement.OnScreen;
import com.nage.components.movement.Physics;
import com.nage.components.movement.PongBall;
import com.nage.components.movement.RandomMovement;
import com.nage.components.movement.RandomWaypointMovement;
import com.nage.components.movement.Tipping;
import com.nage.components.touch.AddSquareButton;
import com.nage.components.touch.Controllable;
import com.nage.components.touch.PongMovementLeft;
import com.nage.components.touch.PongMovementRight;
import com.nage.components.touch.RemoveSquareButton;
import com.nage.components.touch.ToggleRandomMovement;
import com.nage.components.touch.menu.HostMultiplayerGame;
import com.nage.components.touch.menu.JoinMultiplayerGame;
import com.nage.components.touch.menu.StartSinglePlayer;

public class GameGraphBuilder {

	public static final byte sGAME_GRAPH_PACKET = (byte) 0xCF;
	private static final byte eGAME_GRAPH_PACKET = (byte) 0xDF;

	static ByteArrayOutputStream stream_builder = new ByteArrayOutputStream();

	/**
	 * Flattens a given game graph into a byte array and returns the results. It
	 * performs a depth first search of the graph, by getting each sub element
	 * to perform its own flatten procedure using the stream_builder object.
	 * 
	 * @param graph
	 *            The GameGraph that is to be flattened
	 * @return Byte array of flattened game graph
	 * @throws IOException
	 * @throws GameGraphException 
	 */
	public static byte[] flatten(GameGraph graph) throws IOException, GameGraphException {

		stream_builder.reset();  

		stream_builder.write(sGAME_GRAPH_PACKET);
		if (graph.flatten(stream_builder)) { 
			stream_builder.write(eGAME_GRAPH_PACKET);
		//	GameGraph built = GameGraphBuilder.expand(stream_builder.toByteArray());
		//	Log.d("GameGraphBuilder", "GameGraph flattened and being sent to update client.");
		//	built.print();
			return stream_builder.toByteArray();
		} else {
			throw new IOException("Error building GameGraphPacket");
		}
	}
	
	/**
	 * Expands a byte array into a game graph. It will create a blank game
	 * graph, check the array looks okay to build from, and then call the
	 * expand function of GameGraph with the byte array, and the index that
	 * it has looked into so far.
	 * 
	 * @param b Byte array containing a flattened GameGraph
	 * @return A fresh and rebuilt GameGraph
	 * @throws GameGraphException
	 */
	public static GameGraph expand(byte[] b) throws GameGraphException {
		GameGraph graph = new GameGraph();
		int i = 0;
		
	//	Log.d("GameGraphBuilder", "Syncing GameGraph with server.");
		if(b[i]!=sGAME_GRAPH_PACKET) {
			throw new GameGraphException("Expected sGAME_GRAPH_PACKET. Found "+b[i]);
		}
		i++;
		
		graph.expand(b, i);
		return graph;
		
	}
	
	/**
	 * Used to build a complete GameGraph object from an XML file that defines it.
	 * It will scan through the xml file, building each object and the components
	 * for that object and adding them one by one.
	 * Rules for the XML that this parsing function follows:
	 * 		Root tag must be <level> - this builds the empty GameGraph object
	 * 		Within <level> are individual <game_object> tags.
	 * 
	 * @param xml XML file that defines a gamegraph (stored in /res usually)
	 * @param c Context that game is running in.
	 * @return A GameGraph object that can be used to run a game.
	 */
	public static GameGraph load(XmlResourceParser xml, Context c) {
		GameGraph graph = null;
		GameObject current = null;
		
		try {
			// While not at the end of the document.
			while (xml.getEventType() != XmlResourceParser.END_DOCUMENT) {
				// If we reach a start tag ( < x > )
				if (xml.getEventType() == XmlResourceParser.START_TAG) {
					
					String tag_name = xml.getName(); // Grab the name of that tag

					// Search through all possible tags and try to create the relevant object.
					
			        if (tag_name.equalsIgnoreCase("level")) {
			        	graph = new GameGraph();
			        	graph.setMultiplayer(xml.getAttributeValue(null, "multiplayer"));
			        	graph.enableAccelerometer(xml.getAttributeValue(null, "accelerometer"));
			        	
			        } else if (tag_name.equals("game_object")) {
			        	current = new GameObject(graph);
			        	current.setX(xml.getAttributeValue(null, "x"));
			        	current.setY(xml.getAttributeValue(null, "y"));
			        	current.setName(xml.getAttributeValue(null, "id"));
			        	current.setOwner(xml.getAttributeValue(null, "owner"));

			        }  else if (tag_name.equals("shape")) {
			        	Shape shape = new Shape(current);
			        	shape.setWidth(xml.getAttributeValue(null, "width"));
			        	shape.setHeight(xml.getAttributeValue(null, "height"));
			        	shape.setRGB(xml.getAttributeValue(null, "rgb"));
			        	current.addComponent(shape);
			        	
			        } else if (tag_name.equals("sprite")) {
			        	String resourceName = xml.getAttributeValue(null, "name");
			        	int resourceId = c.getResources().getIdentifier(resourceName, "drawable", c.getPackageName());
			        	Sprite sprite = new Sprite(current, resourceId);
			        	sprite.setWidth(xml.getAttributeValue(null, "width"));
			        	sprite.setHeight(xml.getAttributeValue(null, "height"));
			        	current.addComponent(sprite);

			        } else if (tag_name.equals("physics")) {
			        	Physics physics = new Physics(current);
			        	physics.setMass(xml.getAttributeValue(null, "mass"));
			        	current.addComponent(physics);

			        } else if (tag_name.equals("controllable")) {
				        current.addComponent(new Controllable(current));

			        } else if (tag_name.equals("tipping")) {
			        	current.addComponent(new Tipping(current));
			        	
			        } else if (tag_name.equals("pong_movement_left")) {
			        	current.addComponent(new PongMovementLeft(current));

			        } else if (tag_name.equals("pong_movement_right")) {
			        	current.addComponent(new PongMovementRight(current));

			        } else if (tag_name.equals("pong_ball")) {
							current.addComponent(new PongBall(current));

			        } else if (tag_name.equals("random_movement")) {
			        	current.addComponent(new RandomMovement(current));

			        } else if (tag_name.equals("on_screen")) {
			        	current.addComponent(new OnScreen(current));

			        } else if (tag_name.equals("background")) {
			        	Background b = new Background(current);
			        	b.setRGB(xml.getAttributeValue(null, "rgb"));
			        	current.addComponent(b);

			        }  else if (tag_name.equals("add_square_button")) {
			        	AddSquareButton asb = new AddSquareButton(current);
			        	asb.setWidth(xml.getAttributeValue(null, "width"));
			        	asb.setHeight(xml.getAttributeValue(null, "height"));
			        	current.addComponent(asb);

			        } else if (tag_name.equals("remove_square_button")) {
			        	RemoveSquareButton rsb = new RemoveSquareButton(current);
			        	rsb.setWidth(xml.getAttributeValue(null, "width"));
			        	rsb.setHeight(xml.getAttributeValue(null, "height"));
			        	current.addComponent(rsb);

			        } else if (tag_name.equals("toggle_random_movement")) {
			        	ToggleRandomMovement trm = new ToggleRandomMovement(current);
			        	trm.setWidth(xml.getAttributeValue(null, "width"));
			        	trm.setHeight(xml.getAttributeValue(null, "height"));
			        	current.addComponent(trm);	

			        } else if (tag_name.equals("random_waypoint_movement")) {
			        	current.addComponent(new RandomWaypointMovement(current));

			        } else if(tag_name.equals("collision_manager")) {
						current.addComponent(CollisionManager.newCollisionManager(current));

			        } else if(tag_name.equals("collidable")) {
						current.addComponent(new Collidable(current));

			        } else if(tag_name.equals("pong_collidable")) {
						current.addComponent(new PongBat(current));
				
			        } else if(tag_name.equals("collision_mask")) {
			        	current.addComponent(new CollisionMask(current));

			        } else if (tag_name.equals("start_single_player")) {
			        	StartSinglePlayer ssp = new StartSinglePlayer(current);
			        	ssp.setWidth(xml.getAttributeValue(null, "width"));
			        	ssp.setHeight(xml.getAttributeValue(null, "height"));
			        	current.addComponent(ssp);

			        } else if (tag_name.equals("host_multiplayer_game")) {
			        	HostMultiplayerGame hmg = new HostMultiplayerGame(current);
			        	hmg.setWidth(xml.getAttributeValue(null, "width"));
			        	hmg.setHeight(xml.getAttributeValue(null, "height"));
			        	current.addComponent(hmg);

			        } else if (tag_name.equals("join_multiplayer_game")) {
			        	JoinMultiplayerGame jmg = new JoinMultiplayerGame(current);
			        	jmg.setWidth(xml.getAttributeValue(null, "width"));
			        	jmg.setHeight(xml.getAttributeValue(null, "height"));
			        	current.addComponent(jmg);

			        } else if (tag_name.equals("shape_builder")) {
			        	ShapeBuilder sb = new ShapeBuilder(current, xml.getAttributeValue(null, "number"));
			        	current.addComponent(sb);
			        }
			        
			    // Reached an end tag ( </x> )
				} else if (xml.getEventType() == XmlResourceParser.END_TAG) {

					String tag_name = xml.getName(); // Grab the name of that tag
					
					// If we reach a game_object closing tag, attempt to link up the components
					// within that game object. On success, add it to the game graph, otherwise 
					// make an error and don't add it to the graph.
					if(tag_name.equalsIgnoreCase("game_object")) {
						try {
							current.linkComponents();
						} catch (GameGraphException e) {
							Log.e("GameGraphBuilder", "Cannot add Game Object "+current.m_Name+": Error linking componenets");
							Log.e("GameGraphBuilder", e.getMessage());
							throw new RuntimeException("Badly formed level XML. Cannot run game.");
						}
						graph.add(current);
						Log.d("GameGraphBuilder", "Linked all components in "+current.m_Name);
					}
					
				} else if (xml.getEventType() == XmlResourceParser.TEXT) {
					// text inbetween tags
				}
				xml.next(); // move to the next thing in the xml file
			}
		} catch (XmlPullParserException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (NullPointerException e) {
			e.printStackTrace(); // graph not initialized probably. bad xml!
		}
		
		graph.print();
		return graph;

	}

}

